/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package edu.ccut.titan.component;

import edu.ccut.titan.component.IBundleInfo;
import edu.ccut.titan.component.IComponent;
import edu.ccut.titan.component.TitanComponentException;

import java.util.Map;
import java.util.HashMap;
import java.util.Set;

import org.apache.log4j.Logger;

/**
 * The core Class of component module. It's singleton Object.
 * 
 * <p>
 * Manager all {@link IComponentInfo} in a Titan Application. the key with
 * bundle symbolic name and component id to unique each component, use key can
 * find {@link IComponentInfo} and {@link IComponent} about a component.
 * 
 * @author LeslieGu
 */
public class ComponentManager {

	private Logger logger = Logger.getLogger(ComponentManager.class.getName());

	private static final String KEY_SEPARATOR = "::";

	private Map<String, IBundleInfo> bundleComponentInfos;

	private Map<String, IComponent> components;

	private static ComponentManager instance;

	static {
		instance = new ComponentManager();
	}

	private ComponentManager() {
		this.bundleComponentInfos = new HashMap<String, IBundleInfo>();
		this.components = new HashMap<String, IComponent>();
	}

	public static ComponentManager getInstance() {
		return instance;
	}

	/**
	 * add BundleComponentInfo to manager
	 * 
	 * @param bundleComponentInfo
	 */
	public void addBundleComponentInfo(IBundleInfo bundleComponentInfo) {
		this.bundleComponentInfos.put(bundleComponentInfo
				.getBundleSymbolicName(), bundleComponentInfo);
		this.logger
				.debug("add bundleComponentInfo key \""
						+ bundleComponentInfo.getBundleSymbolicName() + "\"");
	}

	/**
	 * remove BundleComponentInfo by bundle symbolic name from manager
	 * 
	 * @param bundleSymbolicName
	 */
	public void removeBundleComponentInfo(String bundleSymbolicName) {
		if (this.bundleComponentInfos.containsKey(bundleSymbolicName)) {
			IBundleInfo bundleComponentInfo = this.bundleComponentInfos
					.get(bundleSymbolicName);

			for (String componentKey : bundleComponentInfo.getComponentKeys()) {
				this.removeComponent(bundleSymbolicName, componentKey);
			}

			this.bundleComponentInfos.remove(bundleSymbolicName);
			this.logger
					.debug("remove bundleComponentInfo key is : \""
							+ bundleComponentInfo.getBundleSymbolicName() + "\"");
		}

		this.logger
				.warn("failed to remove bundleComponentInfo, because the bundle key : \""
						+ bundleSymbolicName
						+ "\" is not valid");
	}

	/**
	 * Get a {@link IBundleInfo} by symbolic name
	 * 
	 * @param bundleSymbolicName
	 * @return if symbolic name is not exist, return null.
	 */
	public IBundleInfo getBundleComponentInfo(String bundleSymbolicName) {
		return this.bundleComponentInfos.get(bundleSymbolicName);
	}

	/**
	 * Get all bundle symbolic name Set
	 * 
	 * @return
	 */
	public Set<String> getBundleSymbolicNames() {
		return this.bundleComponentInfos.keySet();
	}

	/**
	 * Get a {@link IComponent} by full key.
	 * 
	 * @param componentFullKey
	 *            bundleSymbolicName + "::" + componentKey
	 * @return
	 * @throws TitanComponentException
	 */
	public IComponent getComponent(String componentFullKey)
			throws TitanComponentException {

		int index = componentFullKey.indexOf(KEY_SEPARATOR);

		if (index == -1 || index == 0 || index == componentFullKey.length() - 2) {
			throw new TitanComponentException(componentFullKey
					+ " is not a valid key");
		}

		String bundleSymbolicName = componentFullKey.substring(0, index);
		String componentKey = componentFullKey.substring(index + 2);

		return this.getComponent(bundleSymbolicName, componentKey);
	}

	/**
	 * Get component by bundle symbolic name and component key
	 * 
	 * @param bundleSymbolicName
	 * @param componentKey
	 * @return
	 * @throws TitanComponentException
	 */
	public IComponent getComponent(String bundleSymbolicName,
			String componentKey) throws TitanComponentException {

		String componentFullKey = this.getComponentFullKey(bundleSymbolicName,
				componentKey);
		if (this.components.containsKey(componentFullKey)) {
			return this.components.get(componentFullKey);
		}

		IBundleInfo bundleComponentInfo = this.bundleComponentInfos
				.get(bundleSymbolicName);
		if (bundleComponentInfo == null) {
			throw new TitanComponentException(
					"can't find bundle with symbolicName = ["
							+ bundleSymbolicName + "]");
		}

		IComponentInfo componentInfo = bundleComponentInfo
				.getComponentInfo(componentKey);
		if (componentInfo == null) {
			throw new TitanComponentException(
					"can't find componentInfo with key = [" + componentKey
							+ "] in bundle with symbolicName = ["
							+ bundleSymbolicName + "]");
		}

		IComponent component = componentInfo.createComponent();
		this.components.put(componentFullKey, component);

		return component;
	}

	private void removeComponent(String bundleSymbolicName, String componentKey) {
		String componentFullKey = this.getComponentFullKey(bundleSymbolicName,
				componentKey);

		if (this.components.containsKey(componentFullKey)) {
			this.components.remove(componentFullKey);
		}
	}

	private String getComponentFullKey(String bundleSymbolicName,
			String componentKey) {
		return bundleSymbolicName + KEY_SEPARATOR + componentKey;
	}
}
